热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

go|感受并发编程的乐趣前篇

date:2018-2-1523:55:45title:go|感受并发编程的乐趣前篇学习了ccmouse–googl工程师在慕课网–搭建并行处理管道,感受GO语言魅力,获益匪浅,也

date: 2018-2-15 23:55:45
title: go| 感受并发编程的乐趣 前篇

学习了 ccmouse – googl工程师 在 慕课网 – 搭建并行处理管道,感受GO语言魅力, 获益匪浅, 也想把这份编程的快乐传递给大家.

强烈推荐一下ccmouse大大的课程, 总能让我生出 Google工程师果然就是不一样 之感, 每次都能从简单的 hello world 开始, 一步步 coding 到教程的主题, 并在过程中给予充分的理由 — 为什么要一步步变复杂. 同时也会亲身 踩坑 示范, 干货满满.

内容提要:

  • let’s go: 为什么要使用 go ? swoole2.1 带来的 go + channel 编程体验
  • go’s design: go语言的设计
  • go’s hello-world: go 写 hello world 的各种姿势, 为之后并发编程解决的 big problem 埋下伏笔
  • go’s sort: 内排排序 -> 外部排序
  • go’s io: 二进制文件读写 + 大文件读写加速

另外, ccmouse大大关于语言学习的方法也值得借鉴:

  • 首先, 学习一下语言语法的要点
  • 立刻找一个不那么简单的项目来做, 边做边查文档/stackoverflow

let’s go

swoole2.1 发布了(Swoole 2.1 正式版发布,协程+通道带来全新的 PHP 编程模式), 看着示例代码颇有点 陌生 之感, 看来真想要 写好 协程, 熟悉 go 应当是必选项了.

示例代码, go & channel:

// coroutine
go(function () {
co::sleep(0.5);
echo "hello";
});
go("test");
go([$object, "method"]);
// go + channel
$c3 = new chan(2);
$c4 = new chan(2);
$c3->push(3);
$c3->push(3.1415);
$c4->push(3);
$c4->push(3.1415);
go(function () use ($c3, $c4) {
echo "producer\n";
co::sleep(1);
$data = $c3->pop();
echo "pop[1]\n";
var_dump($data);
});

go 看起来就是一个接收 闭包函数 作为入参的函数, channel(通道)看起来也不过是一个类似 的数据结构(push()pop() 2 种操作). 看到示例代码却感到十分 陌生 — 为什么要这样写呀?

既然是 借鉴至 go 语言, 看来有必要了解一下 go, 来加深一下理解了

go’s design

Google内部的「标准」编程语言:

  • c++: 性能保障部分, 如搜索引擎
  • java: 复杂业务逻辑, 如 adwords, Google docs
  • Python: 大量内部工具
  • go: 新的内部工具, 及其他业务模块

语言对比:

  • c/c++: 性能, 可以做系统开发; 没有繁琐的类型系统, 简单统一化的模块依赖管理, 编译速度飞快
  • java: 垃圾回收 -> 慢, 会影响业务
  • Python: 简单易学, 灵活类型, 函数式编程, 异步IO; 没有编译器静态类型检查

go 设计:

  • 类型检查: 编译时
  • 运行环境: 编译成机器码直接运行(不依赖虚拟机)
  • 编程范式: 面向接口, 函数式编程, 并发编程

go 并发编程:

  • CSP, Communication Sequential Process 模型
  • 不需要锁, 不需要 callback
  • 并行计算 + 分布式

go 线程实现模型 MPG:

  • M: Machine, 一个内核线程
  • P: Processor, M所需的上下文环境
  • G: Goroutine, 代表一段需要被并发执行的 go 语言代码的封装
  • KSE: 内核调度实体
  • 一个 M 和一个 P 关联, 形成一个有效的 G 运行环境, 每个 P 都会包含一个可运行的 G 的队列(runq)

《go| 感受并发编程的乐趣 前篇》 go 线程实现模型 MPG

进程/线程开多了都会涉及到系统进行调度导致的消耗, go 通过 MPG 模型进行映射, 可以并行很多 go协程, 底层自动实现调度

go’s hello world

4 个版本的 hello world:

  • 原始版: 直接 print 输出
  • http版: 使用 http 库输出到 web
  • go版: 开始接触 go协程
  • go+channel版: 开始接触 go协程 + channel数据传递

package main
import (
"fmt"
"net/http"
"time"
)
func main() {
// 原始版
helloWorld1()
// http版
helloWorld2()
// go协程版
for i := 0; i <5000; i++ { // 协程 <-> 线程 之间存在隐射, 具体参考 一书中的「线程实现模型」
go helloWorld3(i)
}
time.Sleep(time.Microsecond) // 添加延时, 协程才有机会在 main() 退出前执行
// go + channel
ch := make(chan string)
for i := 0; i <5; i++ {
go helloWorld4(i, ch)
}
for {
msg := <-ch
fmt.Println(msg)
}
}
func helloWorld1() {
fmt.Println("hello world")
}
func helloWorld2() {
// 为什么要使用指针: 因为参数可以被改变
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
//fmt.Fprintln(writer, "

hello world

")
fmt.Fprintf(writer, "

hello world %v

", request.FormValue("name"))
})
http.ListenAndServe(":8888", nil)
}
func helloWorld3(i int) {
fmt.Printf("hello world from goroutine %v \n", i)
}
func helloWorld4(i int, ch chan string) {
for {
ch <- fmt.Sprintf("hello world %v", i)
}
}

go&#8217;s sort

先科普一下排序的知识:

  • 排序分 内部排序 + 外部排序 两种, 区分在于数据量, 内部排序可以将数据全部放到内存中, 然后进行排序
  • 常见的内部排序算法: 冒泡, 快排, 归并排序等, 其中 快排 是速度最快的不稳定排序算法, 归并排序可以应用于外部排序
  • 归并排序时, 可以一次归并多节点达到加速的效果, 例子中使用 二路归并 来简单演示

再来看看 go 实现的归并排序:

package main
import (
"sort"
"fmt"
)
func main() {
// 内部排序 -> 快排
a := []int{3, 6, 2, 1, 9, 10, 8}
sort.Ints(a)
fmt.Println(a)
// 外部排序 -> 归并排序
c := Merge(inMemSort(arraySource(3, 6, 2, 1, 9, 10, 8)),
inMemSort(arraySource(7, 4, 0, 3, 2, 8, 13)))
// channel 中获取数据: 原始
//for {
// // 风格一
// //if v, ok := <-c; ok {
// // fmt.Println(v)
// //} else {
// // break
// //}
// // 风格二
// v, ok := <-c
// if !ok {
// break
// }
// fmt.Println(v)
//}
// channel 中获取数据: 简写
for v := range c {
fmt.Println(v)
}
}
func arraySource(a ...int) <-chan int { // 指定从 channel 获取数据
out := make(chan int)
go func() {
for _, v := range a { // for + range + _
out <- v
}
close(out) // 数据传递完毕, 关闭channel
}()
return out
}
func inMemSort(in <-chan int) <-chan int {
out := make(chan int)
go func() {
// read into memory
a := []int{}
for v := range in {
a = append(a, v)
}
// sort
sort.Ints(a)
// output
for _, v := range a {
out <- v
}
close(out)
}()
return out
}
func Merge(in1, in2 <-chan int) <-chan int {
out := make(chan int)
go func() {
// 归并的过程要处理某个通道可能没有数据的情况, 代码非常值得一读
v1, ok1 := <-in1
v2, ok2 := <-in2
for ok1 || ok2 {
if !ok2 || (ok1 && v1 <= v2) {
out <- v1
v1, ok1 = <-in1
} else {
out <- v2
v2, ok2 = <-in2
}
}
close(out)
}()
return out
}

go&#8217;s io

文件读写算是一个 常见且简单 的任务, 但是:

  • 如何读写二进制文件呢? int 大小是多少? 大端序小端序?
  • 如何读写大文件, 比如 800m ? 什么是 buffer ? 什么是 flush ?

这一节的代码就用来处理这些问题:

package main
import (
"encoding/binary"
"fmt"
"io"
"os"
"math/rand"
"bufio"
)
func main() {
TestIo()
largeIo()
}
// 文件读写测试
func TestIo() {
// 写
file, err := os.Create("small.in") // 二进制数据
if err != nil {
panic(err) // 遇到错误, 暂时不处理
}
defer file.Close() // 运行结束后, 自动关闭文件
p := randomSource(50) // small.in 的大小 = 8*50
writerSink(file, p)
// 读
file, err = os.Open("small.in")
if err != nil {
panic(err)
}
defer file.Close()
p = readerSource(file)
for v := range p {
fmt.Println(v)
}
}
// 大文件读写
func largeIo() {
const filename = "large.in"
const n = 100000000 // 8*100*1000*100 = 800M
file, err := os.Create(filename)
if err != nil {
panic(err)
}
defer file.Close()
p := randomSource(n)
writer := bufio.NewWriter(file) // io buffer 加快文件读写
writerSink(writer, p)
writer.Flush() // flush 掉 io buffer 中的数据
// 读
file, err = os.Open(filename)
if err != nil {
panic(err)
}
defer file.Close()
// 写, 只测试前 100 个
p = readerSource(bufio.NewReader(file))
count := 0
for v := range p {
fmt.Println(v)
count++
if count >= 100 {
break
}
}
}
func readerSource(reader io.Reader) <-chan int {
out := make(chan int)
go func() {
buffer := make([]byte, 8) // int: 64bit -> 8byte
for {
n, err := reader.Read(buffer)
if n > 0 { // 可能数据不足 8byte
v := int(binary.BigEndian.Uint64(buffer)) // Uint64 -> int
out <- v
}
if err != nil {
break
}
}
close(out)
}()
return out
}
func writerSink(writer io.Writer, in <-chan int) {
for v := range in {
buffer := make([]byte, 8)
binary.BigEndian.PutUint64(buffer, uint64(v))
writer.Write(buffer)
}
}
func randomSource(count int) <-chan int {
out := make(chan int)
go func() {
for i := 0; i out <- rand.Int()
}
close(out)
}()
return out
}

写在最后

go 的「强制」在编程方面感觉优点大于缺点:

  • 强制代码风格: 读/写代码都轻松了不少
  • 强制类型检查: 出错时的错误提示非常友好

书写过程中, 基本根据编译器提示, 就可以把大部分 bug 清理掉.

再次推荐一下 go, 给想要写 并发编程 的程序汪, 就如 ccmouse大大的教程所说:

感受并发编程的乐趣

资源推荐:

  • 首推 ccmouse大大的视频教程: 慕课网 &#8211; 搭建并行处理管道,感受GO语言魅力
  • 同时推荐看完一本书 go并发编程: 讲解并发编程相关知识和 go并发编程原理 上面非常透彻, 几个实战的项目也适合 ccmouse大大推荐的学习方式 &#8212; 不那么简单的项目来练手

推荐阅读
  • 2018年人工智能大数据的爆发,学Java还是Python?
    本文介绍了2018年人工智能大数据的爆发以及学习Java和Python的相关知识。在人工智能和大数据时代,Java和Python这两门编程语言都很优秀且火爆。选择学习哪门语言要根据个人兴趣爱好来决定。Python是一门拥有简洁语法的高级编程语言,容易上手。其特色之一是强制使用空白符作为语句缩进,使得新手可以快速上手。目前,Python在人工智能领域有着广泛的应用。如果对Java、Python或大数据感兴趣,欢迎加入qq群458345782。 ... [详细]
  • vue使用
    关键词: ... [详细]
  • 学习SLAM的女生,很酷
    本文介绍了学习SLAM的女生的故事,她们选择SLAM作为研究方向,面临各种学习挑战,但坚持不懈,最终获得成功。文章鼓励未来想走科研道路的女生勇敢追求自己的梦想,同时提到了一位正在英国攻读硕士学位的女生与SLAM结缘的经历。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
  • SpringBoot整合SpringSecurity+JWT实现单点登录
    SpringBoot整合SpringSecurity+JWT实现单点登录,Go语言社区,Golang程序员人脉社 ... [详细]
  • 如何实现织梦DedeCms全站伪静态
    本文介绍了如何通过修改织梦DedeCms源代码来实现全站伪静态,以提高管理和SEO效果。全站伪静态可以避免重复URL的问题,同时通过使用mod_rewrite伪静态模块和.htaccess正则表达式,可以更好地适应搜索引擎的需求。文章还提到了一些相关的技术和工具,如Ubuntu、qt编程、tomcat端口、爬虫、php request根目录等。 ... [详细]
  • 本文讨论了一个关于正则的困惑,即为什么一个函数会获取parent下所有的节点。同时提出了问题是否是正则表达式写错了。 ... [详细]
  • 本文介绍了[从头学数学]中第101节关于比例的相关问题的研究和修炼过程。主要内容包括[机器小伟]和[工程师阿伟]一起研究比例的相关问题,并给出了一个求比例的函数scale的实现。 ... [详细]
  • javascript  – 概述在Firefox上无法正常工作
    我试图提出一些自定义大纲,以达到一些Web可访问性建议.但我不能用Firefox制作.这就是它在Chrome上的外观:而那个图标实际上是一个锚点.在Firefox上,它只概述了整个 ... [详细]
  • 本文介绍了如何使用Python正则表达式匹配MATLAB的函数语法,包括多行匹配和跨行签名的处理方法。同时,作者还分享了自己遇到的问题和解决方案。 ... [详细]
  • 本文介绍了在使用Python中的aiohttp模块模拟服务器时出现的连接失败问题,并提供了相应的解决方法。文章中详细说明了出错的代码以及相关的软件版本和环境信息,同时也提到了相关的警告信息和函数的替代方案。通过阅读本文,读者可以了解到如何解决Python连接服务器失败的问题,并对aiohttp模块有更深入的了解。 ... [详细]
  • Ubuntu安装常用软件详细步骤
    目录1.GoogleChrome浏览器2.搜狗拼音输入法3.Pycharm4.Clion5.其他软件1.GoogleChrome浏览器通过直接下载安装GoogleChro ... [详细]
  • 数字账号安全与数据资产问题的研究及解决方案
    本文研究了数字账号安全与数据资产问题,并提出了解决方案。近期,大量QQ账号被盗事件引起了广泛关注。欺诈者对数字账号的价值认识超过了账号主人,因此他们不断攻击和盗用账号。然而,平台和账号主人对账号安全问题的态度不正确,只有用户自身意识到问题的严重性并采取行动,才能推动平台优先解决这些问题。本文旨在提醒用户关注账号安全,并呼吁平台承担起更多的责任。令牌云团队对此进行了长期深入的研究,并提出了相应的解决方案。 ... [详细]
author-avatar
宇中尘粒
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有